package algo_lignes_sites.data;

import algo_lignes_sites.data.junction.A_Junction;
import algo_lignes_sites.data.junction.JunctionDER;
import algo_lignes_sites.data.junction.JunctionREF;
import algo_lignes_sites.data.path.A_Path;
import algo_lignes_sites.data.path.PathDER;
import algo_lignes_sites.data.path.PathREF;
import algo_lignes_sites.data.segment.A_AlgolineSegment;
import algo_lignes_sites.data.segment.SegmentDER;
import algo_lignes_sites.data.segment.SegmentREF;
import algo_lignes_sites.params.Params_ls;
import commons.Synchronizer;
import commons.data.I_Data;
import commons.io.in.Point_in;
import commons.io.in.Segment_in;
import commons.logs.Logger;
import commons.params_appli.ParamsAppli;
import commons.spatialindex.simplegrid.SimpleGridSPI;
import commons.utils.UtilsTime;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import gnu.trove.set.hash.THashSet;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;

public class Data_ls implements I_Data {

	static public Data_ls data_ls;

	// Ensemble des paramétrages
	public Params_ls params;

	// Nb de CPU pour le calcul
	private int nbCPU=0;

	// Ensemble de toutes les junctions (certaines junctions peuvent etre des nodes)
	//  (les junctions stockées dans les vJunction des PathXXX pointe dessus)
	private ArrayList<JunctionREF> vJunctionsREF;
	private ArrayList<JunctionDER> vJunctionsDER;

	// Pointe uniquement vers les nodes, ce qui accélère le parcours lors de leur traitement
//	private ArrayList<JunctionREF> vNodeREF;
	private ArrayList<JunctionDER> vNodeDER;

	private ArrayList<SegmentREF> vSegmentREF;
	private ArrayList<SegmentDER> vSegmentDER;

	private ArrayList<PathREF> vPathREF;
	private ArrayList<PathDER> vPathDER;

	// objet non generalises pour cause de : echelle=0
	// vont servir (notamment les segments) pour raccord visuel aux segments généralisés
	//  sur les frontieres de decoupage au niveau des zones
	// ces 2 vecteur d'objet sont sauvegarder en sortie
	private ArrayList<Segment_in> vSegNonGeneralise;
	private ArrayList<Point_in> vNodeNonGeneralise;


	// Emprise des données (basée sur les coordonnées d'origine)
	private Rect_d rectEmprise;
	private Rect_d rectEmprise_protection; // un peu plus gros, pour détection de divergence
	private boolean bDivergence = false;
	public boolean hasDiverged() {return bDivergence;}

	// Des index spatiaux pour tout ce petit monde
	private SimpleGridSPI spiPathDER;
	private SimpleGridSPI spiPathREF;

	private SimpleGridSPI spiJunctionDER;
	private SimpleGridSPI spiJunctionREF;

	private SimpleGridSPI spiNodeDER;

	private SimpleGridSPI spiSegmentDER;
	private SimpleGridSPI spiSegmentREF;

	// contient copie des segments d'origine (apres filtre principal, mais avant tout pretraitement)
	// pour affichage ultérieur pour info (debug)
	private SimpleGridSPI spiSegSource;

	private SimpleGridSPI spiSegNonGeneralise;
	private SimpleGridSPI spiNodeNonGeneralise;

	public double maxDiametreNode_reel_m = 0.0;
	public double maxEspaceMiniAutourNode_reel_m = 0.0;
	public double maxEpaisseurPath_reel_m = 0.0;
	public double maxEpaisseurDissymetrie_reel_m = 0.0;
	public double maxEspaceMiniAutourPath_reel_m = 0.0;

	// Permet à certains SPI de savoir si les données ont changé
	// (si oui, ils se remettent à jour automatiquement, si non, ILS NE SE REMETTENT PAS A JOUR)
	private int versionDataDER;
	public int getVersionDataDER() {return versionDataDER;}

	public double echelleCarteCourante_inv;
	

	public int getNbPath() {return vPathREF.size();}
	public ArrayList<PathDER> getVPathDER() {return vPathDER;}
	public ArrayList<PathREF> getVPathREF() {return vPathREF;}
	public ArrayList<JunctionDER> GetVPointDER() {return vJunctionsDER;}
	public ArrayList<JunctionREF> GetVPointREF() {return vJunctionsREF;}
	public ArrayList<JunctionDER> getVNodeDER() {return vNodeDER;}

	public ArrayList<Segment_in> getVSegNonGeneralise() {return vSegNonGeneralise;}
	public ArrayList<Point_in>   getVNodeNonGeneralise() {return vNodeNonGeneralise;}
	
	private double maxDeplacement_Pondere_Ever;

	private boolean bSnapJonctionToZone;

	public Data_ls(Params_ls params) {
		this.params = params;
		this.nbCPU = ParamsAppli.getParam_i("Autogene.NB_CPU");
		this.versionDataDER = SimpleGridSPI.NO_VERSION;

		// Cette fonction de snap junction->bord de zone peut etre désactivée par l'utilisateur
		this.bSnapJonctionToZone = ParamsAppli.getParam_b("Lignesite.SNAP_JONCTION_TO_ZONE");

		Data_ls.data_ls = this;
	}
	
	public void setData (ArrayList<JunctionREF> vJunctionsREF, ArrayList<PathREF> vPathREF, ArrayList<Segment_in> vSegSource,
			ArrayList<Segment_in> vSegNonGeneralise, ArrayList<Point_in> vNodeNonGeneralise) {
		
		Logger.log_INFO1("-- Finalisation des données dans le Data --");
		
		// On fait addAll plutot que simple = car les vecteur argument vont etre nettoyés par l'appelant !
		this.vJunctionsREF = new ArrayList<JunctionREF>();
		this.vJunctionsREF.addAll(vJunctionsREF); 
		this.vPathREF = new ArrayList<PathREF>();
		this.vPathREF.addAll(vPathREF);
		
		maxDeplacement_Pondere_Ever = 0;


		//**************** Création des données DER ****************************
		Logger.log_INFO2(" -> Création des données DER");
		versionDataDER = 0;
		vJunctionsDER = new ArrayList<JunctionDER>();
		vPathDER = new ArrayList<PathDER>();
		
// BIDOUILLE pour tester des cas pourris les plus mal optimisés au niveau des barbules
//		for (PathREF pathREF : vPathREF)
//			pathREF.reverse();
		
		// On commence par créer l'ensemble des PathDER vides,
		//  car on va devoir y faire référence lors de la création des points
		for (PathREF pathREF : vPathREF) {
			PathDER pathDER = new PathDER(pathREF);
			
			vPathDER.add(pathDER);
		}

		// On dupplique les JunctionsREF en JunctionDER
		for (JunctionREF jREF : vJunctionsREF)
			vJunctionsDER.add(new JunctionDER (jREF));

		// Puis on construit les PathDER (déjà créés mais vides)
		for (PathREF pathREF : vPathREF) {
			PathDER pathDER = pathREF.getPathDER();
			
			// Création des junctions DER
			ArrayList<JunctionREF> vJunctionREF_path = pathREF.getVJunctionREF();
			for (JunctionREF jREF : vJunctionREF_path)
				pathDER.addJunction(jREF.getJunctionDER());
			
			// Recopie la notion de connexion indirecte aux nodes
			int nbNodeConnected = pathREF.getNbNodeConnected();
			for (int j=0; j<nbNodeConnected; j++) {
				JunctionREF nodeREF = (JunctionREF) pathREF.getNodeConnected(j);
				pathDER.addNodeConnected(nodeREF.getJunctionDER());
			}
		}
		


		//******** Création d'un vecteur de Node (sous-ensemble de vJunction ***
		// Ca optimise grandement les traitements ultérieurs
		vNodeDER = new ArrayList<JunctionDER>();
		int nbJunctionDER = vJunctionsDER.size();
		for (int i=0 ; i<nbJunctionDER ; i++) {
			JunctionDER j = vJunctionsDER.get(i);
			if (j.isNode())
				vNodeDER.add(j);
		}


		//**************** Création des vecteurs de Segment ********************
		Logger.log_INFO2(" -> Création des Segments REF et DER");
		// en REF comme en DER
		for (A_Path p : vPathREF)	p.computeVSegment();
		for (A_Path p : vPathDER)	p.computeVSegment();

		//  + un vecteur global dans le Data
		vSegmentREF = new ArrayList<SegmentREF>();
		for (PathREF pathREF : vPathREF)
			for (SegmentREF segREF : pathREF.getVSegmentREF())
				vSegmentREF.add(segREF);
		vSegmentDER = new ArrayList<SegmentDER>();
		for (PathDER pathDER : vPathDER)
			for (SegmentDER segDER : pathDER.getVSegmentDER())
				vSegmentDER.add(segDER);
		// On lie les segments REF et DER (ils sont nécessairement dans le meme ordre dans les vecteurs globaux
		int nbSegment = vSegmentDER.size();
		for (int i = 0; i<nbSegment ; i++) {
			SegmentDER segDER = vSegmentDER.get(i);
			SegmentREF segREF = vSegmentREF.get(i);
			A_AlgolineSegment.setLinkedREFDER(segREF, segDER);
		}


		//******************* Remplissage des index spatiaux *******************
		Logger.log_INFO2(" -> 1ere alimentation des index spatiaux");
		
		computeRectEmprise();
		
		spiPathDER = new SimpleGridSPI("Index Path DER", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsDER.size())); // les path sont geres et optimises dans le SPI a la taille segment et donc le spi doit etre dimensionne comme puor gerer des junctions
		spiNodeDER = new SimpleGridSPI("Index Nodes DER", rectEmprise, SimpleGridSPI.getTailleFor(vNodeDER.size()));
		
		spiJunctionDER = new SimpleGridSPI("Index Jonctions DER", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsDER.size()));
		spiSegmentDER = new SimpleGridSPI("Index Segment DER", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsDER.size()));

		spiPathREF = new SimpleGridSPI("Index Path REF", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsREF.size())); // que pour l'affichage
		spiJunctionREF = new SimpleGridSPI("Index Jonctions REF", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsREF.size())); // que pour l'affichage
		spiSegmentREF = new SimpleGridSPI("Index Segment REF", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsREF.size())); // que pour l'affichage

		spiSegSource = new SimpleGridSPI("Index Segment Source", rectEmprise, SimpleGridSPI.getTailleFor(vJunctionsREF.size())); // il y a bcp plus d'objet que de juncton REF mais on ne va pas encombrer la memoire pour du simple affichage debug ...

		spiSegNonGeneralise = new SimpleGridSPI("Index Segment non generalises", rectEmprise, SimpleGridSPI.getTailleFor(vSegNonGeneralise.size())); // que pour l'affichage
		spiNodeNonGeneralise = new SimpleGridSPI("Index Node non generalises", rectEmprise, SimpleGridSPI.getTailleFor(vNodeNonGeneralise.size())); // que pour l'affichage


		spiPathDER.addObjects(vPathDER, versionDataDER);
		spiPathREF.addObjects(vPathREF, SimpleGridSPI.NO_VERSION);

		spiJunctionDER.addObjects(vJunctionsDER, versionDataDER);
		spiJunctionREF.addObjects(vJunctionsREF, SimpleGridSPI.NO_VERSION);

		spiNodeDER.addObjects(vNodeDER, versionDataDER);

		spiSegmentDER.addObjects(vSegmentDER, versionDataDER);
		spiSegmentREF.addObjects(vSegmentREF, SimpleGridSPI.NO_VERSION);

		spiSegSource.addObjects(vSegSource, SimpleGridSPI.NO_VERSION);

		spiSegNonGeneralise.addObjects(vSegNonGeneralise);
		spiNodeNonGeneralise.addObjects(vNodeNonGeneralise);


		//**************** Fin de divers pré-traitements ***********************
		// Il faut avoir rempli les SPI avant, pour certains de ces traitements !

		setEchelleCarteCourante(1/params.echelleInit);
		
/* OLD V19
		for (PathDER p : vPathDER)
			p.prepareDirectionData();
*/
		
		for (A_Junction j : vJunctionsREF)
			j.computeBHasDyssimetrie();
		for (A_Junction j : vJunctionsDER)
			j.computeBHasDyssimetrie();


		computeInfluencement();
		
		
		assert (checkTopologie());

		this.vSegNonGeneralise = new ArrayList<>(vSegNonGeneralise);
		this.vNodeNonGeneralise = new ArrayList<>(vNodeNonGeneralise);
		
		// Ultime pre-traitement, qui necessite d'avoir les junctionDER construite
		//  -> raccord entre les segment_in supprimés (cause echelle=0) et une
		//     jDER correspondante pour permettre de "boucher" d'éventuels trous aux
		//     frontières des zones de coupure
		raccordeSegmentsNonGeneralises();

	}

	
	// fait pointer les segment_in supprimé durant les pretraitement vers des junctionDER
	private void raccordeSegmentsNonGeneralises() {
		for (Segment_in seg : vSegNonGeneralise) {
			JunctionDER jDER;
			jDER = getJDER(seg.pt1);
			if (jDER != null)
				seg.pt1 = jDER; // on fait le lien pour que le seg suivent la jDER
			jDER = getJDER(seg.pt2);
			if (jDER != null)
				seg.pt2 = jDER; // on fait le lien pour que le seg suivent la jDER
		}
	}
	// Recherche une JunctionDER superposée à pt, si pas trouvé, renvoie null, sinon renvoie la JDER trouvée
	private JunctionDER getJDER(Point_d pt) {
		ArrayList<JunctionDER> vJProches = (ArrayList) spiJunctionDER.getObjectsThatMayIntersectWith(pt);
		for (JunctionDER j : vJProches)
			if (j.equalsXY(pt))
				return j;
		return null;
	}

	
	private void computeRectEmprise() {
		if (vJunctionsREF.isEmpty()) {
			rectEmprise = null;
			rectEmprise_protection = null;
		} else {

			rectEmprise = new Rect_d(vJunctionsREF.get(0));
			for (int i = 1; i < vJunctionsREF.size(); i++)
				rectEmprise.extendToPoint(vJunctionsREF.get(i));
		}

		// idem en plus gros, pour détection de divergence du modèle
		rectEmprise_protection = rectEmprise.getCopy();
		rectEmprise_protection.expand(ParamsAppli.getParam_d("LigneSite.FACTEUR_DETECTION_DIVERGENCE"));
	}

	@Override
	public Rect_d getRectEmprise() {return rectEmprise;}



	// Calcul de l'influencement des Nodes et PtMultiple sur les junctions alentour
	// Cette influence permettra d'en déduire des atténuations de répulsion et des forces d'entrainement
	//  pendant le calcul des forces
	// On le calcule dès le départ, donc avec les dimensions finales des nodes et des path (échelle finale)
	private void computeInfluencement() {

		Logger.log_INFO1("-- Calcul des influencement des nodes et points multiples --");

		// Reinitialisation des influencements
		// normalement pas besoin car on ne le fait qu'une fois
		int nbJunctionDER = vJunctionsDER.size();
		for (int i = 0 ; i< nbJunctionDER ; i++) {
			JunctionDER junctionDER = vJunctionsDER.get(i);
			junctionDER.reinitInfluenceur_NODE();
			junctionDER.reinitInfluenceur_PTMULTIPLE();
		}

		// Calcul des influencements des points multiples sur les jonctions alentours
		for (int i = 0 ; i< nbJunctionDER ; i++) {
			JunctionDER junctionDER = vJunctionsDER.get(i);
			if (junctionDER.isPtMultiple())
				junctionDER.computeInfluencement();
		}

		// calcul des atténuation
		for (int i = 0 ; i< nbJunctionDER ; i++) {
			JunctionDER junctionDER = vJunctionsDER.get(i);
			
			// Cette atténuation est constante durant le traitement, donc on la calcule avant le traitement
			junctionDER.computeFactAttenuationRepulsion();
		}
	}


	// Normalement, les SPI s'updatent tout seul lors d'appels à getVPathDER() etc.
	//  dès qu'ils voient que la version des données a changé
	// Mais pb si multiThread car du coup l'update du SPI intervient dès le premier appel
	//  à computeForce : pdt l'update, un autre thread peut interroger le SPI lors d'un
	//  autre computeForce concurent...
	//  d'ou appel à cette fonction en dehors du multiThread, en début d'appel à Data.computeForce
	private void updatesSPIs() {
//		spiPathDER.updateVersionData(versionDataDER);
		spiNodeDER.updateVersionData(versionDataDER);
		spiSegmentDER.updateVersionData(versionDataDER);
		//spiJunctionDER.updateVersionData(versionDataDER); // pas utilisé en multithread
	}


	@Override
	public ArrayList getData (DataType dataType, Rect_d rectVisu) {
		switch(dataType) {

			case PATH_DER_LS:
				return getVPathDER(rectVisu);
			case PATH_REF_LS:
				return getVPathREF(rectVisu);
			case JUNCTION_DER_LS:
				return getVJunctionDER(rectVisu);
			case JUNCTION_REF_LS:
				return getVJunctionREF(rectVisu);
			case NODE_DER_LS:
				return getVNodeDER(rectVisu);
			case NODE_REF_LS:
				return getVNodeREF(rectVisu);
			case SEGMENT_DER_LS:
				return getVSegmentDER(rectVisu);
			case ZONE:
				return params.vZone;
			case SEGMENT_REF_LS:
				return getVSegmentREF(rectVisu);
			case SEGMENT_SOURCE_LS:
				return spiSegSource.getObjectsThatMayIntersectWith(rectVisu);
			case SEGMENT_NON_GENERALISE_LS:
				return spiSegNonGeneralise.getObjectsThatMayIntersectWith(rectVisu);
			case NODE_NON_GENERALISE_LS:
				return spiNodeNonGeneralise.getObjectsThatMayIntersectWith(rectVisu);
				
			default: assert(false); return null;
		}
	}

	// renvoit les path DER présents dans rect_m
	public ArrayList getVPathDER(Rect_d rect_m) {
		// suivant l'évolution du numéro de version, l'index spatial se re-calculera automatiquement
		return spiPathDER.getObjectsThatMayIntersectWith(rect_m, versionDataDER);
	}
	// renvoit les path REF présents dans rectVisu
	private ArrayList getVPathREF(Rect_d rect_m) {
		// pas besoin de version, les données REF ne bougent pas
		return spiPathREF.getObjectsThatMayIntersectWith(rect_m, SimpleGridSPI.NO_VERSION);
	}
	// renvoit les Segment DER présents dans rect_m
	public ArrayList getVSegmentDER(Rect_d rect_m) {
		// suivant l'évolution du numéro de version, l'index spatial se re-calculera automatiquement
		return spiSegmentDER.getObjectsThatMayIntersectWith(rect_m, versionDataDER);
	}
	// renvoit les Segment REF présents dans rectVisu
	private ArrayList getVSegmentREF(Rect_d rect_m) {
		// pas besoin de version, les données REF ne bougent pas
		return spiSegmentREF.getObjectsThatMayIntersectWith(rect_m, SimpleGridSPI.NO_VERSION);
	}
	// renvoit les jonctions DER présentes dans rectVisu
	private ArrayList getVJunctionDER(Rect_d rect_m) {
		// suivant l'évolution du numéro de version, l'index spatial se re-calculera automatiquement
		return spiJunctionDER.getObjectsThatMayIntersectWith(rect_m, versionDataDER);
	}
	// renvoit les jonctions REF présentes dans rectVisu
	private ArrayList getVJunctionREF(Rect_d rect_m) {
		// pas besoin de version, les données REF ne bougent pas
		return spiJunctionREF.getObjectsThatMayIntersectWith(rect_m, SimpleGridSPI.NO_VERSION);
	}

	// renvoit les Nodes DER présentes dans rectVisu
	public ArrayList getVNodeDER(Rect_d rect_m) {
		// suivant l'évolution du numéro de version, l'index spatial se re-calculera automatiquement
		return spiNodeDER.getObjectsThatMayIntersectWith(rect_m, versionDataDER);
	}
	
	// TODO PERF : si bcp d'appel, les filtrer une bonne fois pour toutes dans un vecteur a part
	//  et un SPI a part
	// renvoit les nodes REF présentes dans rectVisu
	private ArrayList getVNodeREF(Rect_d rect_m) {
		// pas besoin de version, les données REF ne bougent pas
		return filtreOnlyNode(spiJunctionREF.getObjectsThatMayIntersectWith(rect_m, SimpleGridSPI.NO_VERSION));
	}

	private ArrayList<A_Junction> filtreOnlyNode(ArrayList vJunction) {
		ArrayList<A_Junction> vRes = new ArrayList<A_Junction>();
		for (Object j : vJunction)
			if (((A_Junction)j).isNode())
				vRes.add((A_Junction)j);
		return vRes;
	}

	
	// L'echelle de la carte a changé, il faut mettre à jour les épaisseurs des path
	public void setEchelleCarteCourante (double echelleCarteCourante_inv) {
		assert echelleCarteCourante_inv > 1;
		this.echelleCarteCourante_inv = echelleCarteCourante_inv;
		UtilsTime.startTimer("Changement d'échelle");

		// Il faut commencer par les path, car l'épaisseur ainsi calculée va etre utilisée
		//  pour les junctions autres que NODE
		majContraintesPath(echelleCarteCourante_inv);
		majContraintesNodes(echelleCarteCourante_inv);
		
		UtilsTime.stopTimer("Changement d'échelle");
		Logger.log_DEBUG1(UtilsTime.getStringDuree("Changement d'échelle"));
	}

	
	private void majContraintesPath(double echelleCarteCourante_inv) {
		// Mise à jour des dimensions pour les path
		maxEpaisseurPath_reel_m = 0.0;
		maxEspaceMiniAutourPath_reel_m = 0.0;

		for (PathREF pathREF : vPathREF)
			pathREF.majContrainteGeom_echelleCouranteChanged(echelleCarteCourante_inv);
		for (PathDER pathDER : vPathDER) {
			pathDER.majContrainteGeom_echelleCouranteChanged(echelleCarteCourante_inv);
			maxEpaisseurPath_reel_m = Math.max(maxEpaisseurPath_reel_m, pathDER.epaisseurTrait_reel_m);
			maxEpaisseurDissymetrie_reel_m = Math.max(maxEpaisseurDissymetrie_reel_m, pathDER.epaisseurDissymetrie_reel_m);
			maxEspaceMiniAutourPath_reel_m = Math.max(maxEspaceMiniAutourPath_reel_m, pathDER.espaceAutour_reel_m);
		}
	}

	
	// Mise à jour des diametre pour les nodes
	private void majContraintesNodes(double echelleCarteCourante_inv) {
		// qques stats pour accélérer certains traitements ultérieurs
		maxDiametreNode_reel_m = 0.0;
		maxEspaceMiniAutourNode_reel_m = 0.0;

		for (JunctionREF jREF : vJunctionsREF) {
			jREF.majContrainteGeom_echelleCouranteChanged(echelleCarteCourante_inv);
		}
		for (JunctionDER jDER : vJunctionsDER) {
			jDER.majContrainteGeom_echelleCouranteChanged(echelleCarteCourante_inv);

			maxDiametreNode_reel_m = Math.max(maxDiametreNode_reel_m, jDER.diametre_reel_m);
			maxEspaceMiniAutourNode_reel_m = Math.max(maxEspaceMiniAutourNode_reel_m, jDER.espaceAutour_reel_m);
		}
	}

	
	public void step(String msg) {
		UtilsTime.startTimer("calcul");
		Synchronizer.beginCalcul();
		computeForces();
		bougeReseau();
		Synchronizer.endCalcul();
		UtilsTime.stopTimer("calcul");
		Logger.log_INFO2(msg + "  " + UtilsTime.getStringDuree("calcul"));

//		System.out.println("MEMORY : " + Runtime.getRuntime().freeMemory());
	}



	public void computeForces() {
		UtilsTime.startTimer(" - computeForces");

		// On le fait en dehors du multi-thread, car sinon, au 1er appel par le 1er thread, le SPI se met à jour
		//  et un autre thread peut vouloir requeter le SPI pendant ce temps ce qui crée un gros souci ...
		updatesSPIs();

		// Calcul des computeForce, dans plusieurs Thread éventuellement

		// Optimisation v12
		// Avant, on donnait le 1er quart des données au 1er thread, le 2eme au 2eme etc.
		// cela generait dans certains cas des thread qui duraient plus longtemps que d'autres
		// (par ex. un thread qui se tape tout le temps des zones denses longues à traiter
		//  au final, on pouvait n'être qu'à 50% de CPU
		// En v12, on répartit mieux le travail.
		// Une premiere idée a été de mélanger vJunctionDER :
		// Collections.shuffle(vJunctionsDER);
		// Ceci a bien répartit la charge sur l'ensemble des CPU, mais au final,
		//  l'ensemble du traitement était plus long ???? !!!!
		// Visiblement, l'algo n'aime pas qu'on change l'odre des junctions ?? !!
		// 2eme idée, qui fonctionne : chaque Thread prend les junctions, 1 sur 4

		ComputeForceThread tabThread[] = new ComputeForceThread[nbCPU];

		// Création des threads
		for (int numThread=0 ; numThread<nbCPU ; numThread++)
			tabThread[numThread] = new ComputeForceThread(numThread, this);
		// On lance tous les threads
		for (int i=0 ; i<nbCPU ; i++)
			tabThread[i].start();
		// Puis on attend qu'ils aient tous terminés
		try {
		for (int i=0 ; i<nbCPU ; i++)
			tabThread[i].join();
		} catch (InterruptedException ex) {}


		UtilsTime.stopTimer(" - computeForces");
		Logger.log_DEBUG1(UtilsTime.getStringDuree(" - computeForces"));
	}



	public void bougeReseau() {
		UtilsTime.startTimer(" - bougeReseau");
		try {

		//**********************************************************************
		// Déplacement de chaque point, suivant ses forces
		// Création des thread
		BougeJunctionThread tabThread1[] = new BougeJunctionThread[nbCPU];
		for (int numThread=0 ; numThread<nbCPU ; numThread++)
			tabThread1[numThread] = new BougeJunctionThread(numThread);
		// On lance tous les thread
		for (int i=0 ; i<nbCPU ; i++) tabThread1[i].start();
		// Puis on attend qu'ils aient tous terminé
		for (int i=0 ; i<nbCPU ; i++) tabThread1[i].join();



		//**********************************************************************
		// Recalcul des angles et des bounding box des segments
		UtilsTime.startTimer("  -> dont calcul angle");
		// Création de tous les thread
		UpdateSegmentThread tabThread2[] = new UpdateSegmentThread[nbCPU];
		for (int numThread=0 ; numThread<nbCPU ; numThread++)
			tabThread2[numThread] = new UpdateSegmentThread(numThread);
		// On lance tous les thread
		for (int i=0 ; i<nbCPU ; i++) tabThread2[i].start();
		// Puis on attend qu'ils aient tous terminé
		for (int i=0 ; i<nbCPU ; i++) tabThread2[i].join();
		UtilsTime.stopTimer("  -> dont calcul angle");

/* INUTILE A PARTIR DE V20
		// Les données utiles à la gestion des dissymetrie dépendent de la positions des junctions
		//  donc il faut mettre à jour ces données apres tout mouvement dans les junctions
		for (JunctionDER j : vJunctionsDER)
			j.updateDirectionData();
*/

		} catch (InterruptedException ex) {}
		//**********************************************************************

		// On incrémente la version des données
		// Cela permettra à l'index spatial de savoir qu'il faut se re-remplir, par ex.
		versionDataDER++;

		UtilsTime.stopTimer(" - bougeReseau");
		Logger.log_DEBUG1(UtilsTime.getStringDuree(" - bougeReseau"));
		Logger.log_DEBUG1(UtilsTime.getStringDuree("  -> dont calcul angle"));
	}



	/**
	 * Thread dédié au calcul de computeForce, pour répartition sur plusieurs CPU
	 */
	private class ComputeForceThread extends Thread {
		private int numThread;
		private Data_ls data;
		public ComputeForceThread(int numThread, Data_ls data) {
			this.numThread = numThread;
			this.data = data;
		}

		@Override
		public void run() {
			
			int nbJ = vJunctionsDER.size();
			for (int i=numThread ; i<nbJ ; i+=nbCPU) {
				JunctionDER jDER = vJunctionsDER.get(i);
				jDER.resetForces();
				if (jDER.isNode())
					jDER.computeForcesRepuls(data);
				jDER.computeForceEntrainement();
				jDER.computeAttractPositionInit();
			}
			
			// ALGO V20 : repulsion sur les segments au lieu des junctions
			int nbS = vSegmentDER.size();
			for (int i=numThread ; i<nbS ; i+=nbCPU) {
				SegmentDER segDER = vSegmentDER.get(i);
				segDER.resetForces();
				segDER.computeForcesRepuls(data);
				segDER.computeForceRespectForme();
			}
			
		}
	}


	private class BougeJunctionThread extends Thread {
		private int numThread;
		public BougeJunctionThread(int numThread) {this.numThread = numThread;}

		@Override
		public void run() {
			int nbJ = vJunctionsDER.size();
			for (int i=numThread ; i<nbJ ; i+=nbCPU) {
				JunctionDER jDER = vJunctionsDER.get(i);

				double old_x = jDER.x;
				double old_y = jDER.y;

				jDER.bouge(params);

				if (bSnapJonctionToZone)
					jDER.snapToZone(params, old_x, old_y);

				//détection de divergence du modèle
				if (jDER.x < rectEmprise_protection.xMin || jDER.x > rectEmprise_protection.xMax ||
					jDER.y < rectEmprise_protection.yMin || jDER.y > rectEmprise_protection.yMax)
					bDivergence = true;
			}
		}
	}

	private class UpdateSegmentThread extends Thread {
		private int numThread;
		public UpdateSegmentThread(int numThread) {this.numThread = numThread;}

		@Override
		public void run() {
			int nbJ = vSegmentDER.size();
			for (int i=numThread ; i<nbJ ; i+=nbCPU) {
				SegmentDER seg = vSegmentDER.get(i);
				seg.updateAngle();
				seg.updateBound();
			}
		}
	}


	private boolean checkTopologie() {
		Logger.log_INFO1(" -> checking topology");

		assert (vJunctionsDER.size() == vJunctionsREF.size());
		assert (vPathDER.size() == vPathREF.size());

		//*************  Junction REF ******************************************
		
		// Remarque : on devrait tester si toutes les junctions des path sont bien presente
		//  dans le vecteur global.
		// Cependant, ce n'est pas la peine, car lors de la creation des donnees DER
		//  l'erreur aurait deja ete detectee : en effet, on cree les jDER a partir de
		//  ce vecteur global, donc si une junction REF est dans un path et pas dans le vecteur
		//  global, alors elle n'aura pas de junction DER ! et ca plantera lors de la creation
		//  du path DER correspondant
		
		// Recherche de doublons de junctions
		for (JunctionREF jREF : vJunctionsREF) {
			Rect_d rect = new Rect_d (jREF, 1, 1);
			ArrayList<JunctionREF> vJREF_proche = this.getVJunctionREF(rect);
			for (JunctionREF jREF_2 : vJREF_proche) {
				if (jREF_2!=jREF && jREF_2.equalsXY(jREF))
					assert(false) : jREF.getInfoDetaillee() + " \n\n  " + jREF_2.getInfoDetaillee() + " => (" + jREF.x + " , " + jREF.y + ")";
			}
		}
		
		// Meme s'il n'y a pas de doublon sur les junctions, il pourrait y avoir
		// une reference multiple de la meme junction dans le vecteur global -> ces junctions seraient calculees plusieurs fois ...
		THashSet<JunctionREF> setJREF = new THashSet<JunctionREF>(vJunctionsREF.size());
		for (JunctionREF jREF : vJunctionsREF)
			if (!setJREF.add(jREF))
				assert false : "Junction REF referencee en doublon : " + jREF;



		for (JunctionREF jREF : vJunctionsREF) {
			int nbPathContenant = jREF.getNbPathContenant();
			
			switch (jREF.getTypeJunction()) {

				case STD :
					if (nbPathContenant!=1)
						assert false : "STD / nbPath=" + nbPathContenant + " : " + " MAIN PATH=" + jREF.getMainPathContenant() + jREF.getInfoDetaillee();
					break;

				case CONNECT_2 :
					if (nbPathContenant!=2)
						assert false : "CONNECT_2 / nbPath=" + nbPathContenant + " : " + " MAIN PATH=" + jREF.getMainPathContenant() + jREF.getInfoDetaillee();
					if (jREF.getPathContenantNum(0)==jREF.getPathContenantNum(1))
						assert false;
					// Le point doit être extrémité de ses 2 Path
					
					for (int i=0 ; i<2 ; i++) {
						A_Path pathREF = jREF.getPathContenantNum(i);
						if (!jREF.isExtremite(pathREF))
							assert false;
					}
					break;

				case CONNECT_3 :
					if (nbPathContenant<3)
						assert false : "CONNECT_3 / nbPath=" + nbPathContenant + " : " + " MAIN PATH=" + jREF.getMainPathContenant() + jREF.getInfoDetaillee();
					if (jREF.getPathContenantNum(0)==jREF.getPathContenantNum(1) ||
						jREF.getPathContenantNum(0)==jREF.getPathContenantNum(2) ||
						jREF.getPathContenantNum(1)==jREF.getPathContenantNum(2) )
						assert(false);
					// Le point doit être extrémité de tous ses Path
					for (int i=0 ; i<nbPathContenant ; i++) {
						A_Path pathREF = jREF.getPathContenantNum(i);
						if (!jREF.isExtremite(pathREF))
							assert false;
					}
					break;

				case NODE :
					// Le point doit être extrémité de tous ses Path
					for (int i=0 ; i<nbPathContenant ; i++) {
						A_Path pathREF = jREF.getPathContenantNum(i);
						if (!jREF.isExtremite(pathREF))
							assert false;
					}
					break;

				case INTERSECT :
					// Il doit y avoir au moins un path traversant, ie il doit y avoir au moins 1 path dont jREF n'est pas (que) l'extrémité
					if (jREF.getVPathContenant_traversant().isEmpty())
						assert false : jREF.getInfoDetaillee();
					break;
				
				default:
					assert false : "type de junction incorect : " + jREF.getTypeJunction();
						
			}

			// Le point doit faire partie des path auquel il prétend être connecté
			for (int i=0 ; i<nbPathContenant ; i++) {
				A_Path pathREF = jREF.getPathContenantNum(i);
				if (pathREF.getIndexOfThisJunction(jREF) == -1)
					assert false;
			}
		}
		
				
		//**************** Junction DER ****************************************
		
		// Meme s'il n'y a pas de doublon sur les junctions, il pourrait y avoir
		// une reference multiple de la meme junction dans le vecteur global -> ces junctions seraient calculees plusieurs fois ...
		THashSet<JunctionDER> setJDER = new THashSet<JunctionDER>(vJunctionsDER.size());
		for (JunctionDER jDER : vJunctionsDER)
			if (!setJDER.add(jDER))
				assert false : "Junction DER referencee en doublon : " + jDER;

		
		for (JunctionDER jDER : vJunctionsDER) {
			int nbPathContenant = jDER.getNbPathContenant();

/* OLD V19
			//********* Donnée de direction ************************************
			// La junction doit posséder au moins autant de données de direction que de path la contenant
			//  (il peut y en avoir plus dans le cas d'un path rebouclant sur lui-même)
			assert(jDER.getNbDirectionData() >= nbPathContenant);
*/
			
			switch (jDER.getTypeJunction()) {
				case STD :
					if (nbPathContenant!=1)
						assert false;
					break;

				case CONNECT_2 :
					if (nbPathContenant!=2)
						assert false;
					if (jDER.getPathContenantNum(0)==jDER.getPathContenantNum(1))
						assert false;
					// Le point doit être extrémité de ses 2 Path
					for (int i=0 ; i<2 ; i++) {
						A_Path pathDER = jDER.getPathContenantNum(i);
						if (!jDER.isExtremite(pathDER))
							assert false;
					}
					break;

				case CONNECT_3 :
					if (nbPathContenant<3)
						assert false;
					if (jDER.getPathContenantNum(0)==jDER.getPathContenantNum(1) ||
						jDER.getPathContenantNum(0)==jDER.getPathContenantNum(2) ||
						jDER.getPathContenantNum(1)==jDER.getPathContenantNum(2) )
						assert(false);
					// Le point doit être extrémité de tous ses Path
					for (int i=0 ; i<nbPathContenant ; i++) {
						A_Path pathDER = jDER.getPathContenantNum(i);
						if (!jDER.isExtremite(pathDER))
							assert false;
						}
					break;

				case NODE :
					// Le point doit être extrémité de tous ses Path
					for (int i=0 ; i<nbPathContenant ; i++) {
						A_Path pathDER = jDER.getPathContenantNum(i);
						if (!jDER.isExtremite(pathDER))
							assert false;
					}
					break;

				case INTERSECT :
					// Il doit y avoir au moins un path traversant, ie. il doit y avoir au moins 1 path dont jDER n'est pas (que) l'extrémité
					if (jDER.getVPathContenant_traversant().isEmpty())
						assert false : jDER.getInfoDetaillee();
					break;
					
				default:
					assert false : "type de junction incorect : " + jDER.getTypeJunction();
			}


			// Le point doit faire partie des path auquel il prétend être connecté
			for (int i=0 ; i<nbPathContenant ; i++) {
				A_Path pathDER = jDER.getPathContenantNum(i);
				if (pathDER.getIndexOfThisJunction(jDER) == -1)
					assert false;
			}

		}

		// PATH REF
		for (PathREF pathREF : vPathREF) {
			// Tous les points du Path doivent déclarer être connecté à ce path
			for (JunctionREF jREF : pathREF.getVJunctionREF()) {
				if (!jREF.faitPartieDuPath(pathREF))
					assert false : jREF + " pas déclaré connecté à " + pathREF;
			}
		}

		// PATH DER
		for (PathDER pathDER : vPathDER) {
			// Tous les points du Path doivent déclarer être connecté à ce path
			for (JunctionDER jDER : pathDER.getVJunctionDER()) {
				if (!jDER.faitPartieDuPath(pathDER))
					assert false : jDER + " pas déclaré connecté à " + pathDER;
			}
		}

		return true;
	}

	//***********************  STAT  *******************************************

	// stat des fin de pretraitement
	public void printStats() {
		Logger.log_DEBUG1("");
		Logger.log_DEBUG1("=========================================================");
		Logger.log_DEBUG1("----------- Statistiques diverses------------------------");
		// Les index spatiaux REF ne sont utilisés que pour l'affichage
		//  leur stats sont moins utiles a connaitre que les index des données DER
		Logger.log_DEBUG1("");
		spiPathDER.printStats();
		Logger.log_DEBUG1("");
		spiSegmentDER.printStats();
		Logger.log_DEBUG1("");
		spiJunctionDER.printStats();
		Logger.log_DEBUG1("");
		spiNodeDER.printStats();

		Logger.log_DEBUG1("=========================================================");
		Logger.log_DEBUG1("");
	}

	private double getDistMaxDeplacement() {
		double distMax = 0;
		for (JunctionDER jDER : vJunctionsDER) {
			distMax = Math.max(distMax, jDER.distTo(jDER.getJunctionREF()));
		}
		return distMax;
	}

	// Vitesse relative à largeur de la ligne ou de la taille du node
	public double[] getVitessesRelatives() {
		double vitesseRelativeMoyenne = 0;
		double vitesseRelativePire = 0;

		int nbJunction = vJunctionsDER.size();

		ArrayList<Double> vVitesseRelative = new ArrayList<Double>(nbJunction);

		int nbJunctionWithRealSize = 0;
		for (int i=0; i<nbJunction ; i++) {
			JunctionDER j= vJunctionsDER.get(i);
			
			if (j.diametre_reel_m == 0)
				continue;
			nbJunctionWithRealSize++;
			
			double vitesse = Math.sqrt(j.dxVitesse*j.dxVitesse + j.dyVitesse*j.dyVitesse);
			double vitesseRelative = vitesse / j.diametre_reel_m;

			vVitesseRelative.add(vitesseRelative);

			vitesseRelativeMoyenne += vitesseRelative;

			if (vitesseRelative>vitesseRelativePire)
				vitesseRelativePire = vitesseRelative;
//			if (vitesseRelativePire > 1)
//				vitesseRelativePire = vitesseRelativePire;
		}
		vitesseRelativeMoyenne /= nbJunctionWithRealSize;

		// vitesse des 5% les plus rapides
		double vitesseRelative_5prct = 0;
		Collections.sort(vVitesseRelative);
		int nb_5prct = nbJunctionWithRealSize/20;
		for (int i=0; i<nb_5prct ; i++) {
			vitesseRelative_5prct += vVitesseRelative.get(vVitesseRelative.size() - i - 1);
		}
		vitesseRelative_5prct /= nb_5prct;

		double[] res = {vitesseRelativeMoyenne, vitesseRelative_5prct, vitesseRelativePire};

		return res;
	}


	public int getNbNewIntersect_DER() {
		spiSegmentDER.updateVersionData(versionDataDER);
		spiNodeDER.updateVersionData(versionDataDER);
		int nbIntersect = A_Path.computeNbNewIntersect_visible(spiSegmentDER, (ArrayList)vSegmentDER, spiNodeDER, maxDiametreNode_reel_m);
				
		// System.out.println("----------NB INTERSECT = " + nbIntersect);
		return nbIntersect;
	}

	//***************************************************************************
	//***************************************************************************
	// POST TRAITEMENT
	//***************************************************************************
	//***************************************************************************
	
	private boolean bPostTrtDone = false;
	
	public void postTraitement() {
		if (bPostTrtDone)
			return;
		
		bPostTrtDone = true;
		
		// algo >= v22
		// 1) Nodes fusionnés : on supprime les postes virtuels temporaires durant le calcul
		//    et on recrée au meme endroit les nodes initiaux masqué durant le calcul
		// ATTENTION, on ne recrée pas la topologie : connexité avec les Path lien DER/REF etc .
		// il est donc impossible de poursuivre les calculs : l'appelant doit s'assurer de désactiver les boutons
		
//		THashSet<JunctionDER> setNodeToDelete = new THashSet<>();
		int nbNodeToDelete = 0;
		ArrayList<JunctionDER> vNodeDERToAdd = new ArrayList<>();
		ArrayList<JunctionREF> vNodeREFToAdd = new ArrayList<>();
		
		// 1ere passe de détection des nodes temporaires fusionnés
		for (JunctionDER nodeDERtemp : vNodeDER) {
			if (!nodeDERtemp.nodeData.isNodeTemporaireFusionne()) continue;
			JunctionREF nodeREFtemp = nodeDERtemp.jREF;
			
			// on flag ce node temporaire comme étant à supprimer
//			setNodeToDelete.add(nodeDERtemp);
			nbNodeToDelete++;

			// et on indique qu'il faut ajouter de nouveaux nodes
			for (Point_in node_in : nodeDERtemp.nodeData.vNodeInitFusionne) {
				JunctionREF nodeREF = new JunctionREF(node_in, node_in.attributes_in, node_in.zones, node_in.contrainteGeomNode, node_in.echelleGeneralization, node_in.color, null);
				JunctionDER nodeDER = new JunctionDER(nodeREF);
				
				if (params.fusionNode_defusionFinTrt) {
					// on replace les nodes autour du node DER final , décalé de la meme façon que initialement
					nodeDER.x = nodeDERtemp.x + (node_in.x - nodeREFtemp.x);
					nodeDER.y = nodeDERtemp.y + (node_in.y - nodeREFtemp.y);
				} else {
					// tous les nodes créés sont superposés au node DER final
					nodeDER.x = nodeDERtemp.x;
					nodeDER.y = nodeDERtemp.y;
				}

				vNodeREFToAdd.add(nodeREF);
				vNodeDERToAdd.add(nodeDER);
			}
		}
		
		// 2eme passe pour supprimer les nodes temporaires détectés
		ArrayList<JunctionDER> vNodeDER_new      = new ArrayList<>(vNodeDER.size() - nbNodeToDelete + vNodeDERToAdd.size());
		ArrayList<JunctionDER> vJunctionsDER_new = new ArrayList<>(vJunctionsDER.size() - nbNodeToDelete + vNodeDERToAdd.size());
		ArrayList<JunctionREF> vJunctionsREF_new = new ArrayList<>(vJunctionsREF.size() - nbNodeToDelete + vNodeREFToAdd.size());
		
		for (JunctionDER jDER : vJunctionsDER) {
			if (jDER.isNode()) {
				if (!jDER.nodeData.isNodeTemporaireFusionne()) {
//				if (!setNodeToDelete.contains(jDER)) {
					vNodeDER_new.add(jDER);
					vJunctionsDER_new.add(jDER);
					vJunctionsREF_new.add(jDER.jREF);
				}
			} else {
				// ce n'est pas un node, il n'a donc pas pu etre supprimé ...
				vJunctionsDER_new.add(jDER);
				vJunctionsREF_new.add(jDER.jREF);
			}
		}
		vNodeDER_new.addAll(vNodeDERToAdd);
		vJunctionsDER_new.addAll(vNodeDERToAdd);
		vJunctionsREF_new.addAll(vNodeREFToAdd);
		
		
		vNodeDER = vNodeDER_new;
		vJunctionsDER = vJunctionsDER_new;
		vJunctionsREF = vJunctionsREF_new;
		
		// update complet des SPI
		spiJunctionDER.clear();
		spiJunctionDER.addObjects(vJunctionsDER);
		
		spiJunctionREF.clear();
		spiJunctionREF.addObjects(vJunctionsREF);
		
		spiNodeDER.clear();
		spiNodeDER.addObjects(vNodeDER);
		
		//*****************************************************
		// Correction v1.0.2 10/03/2015 -> les segments virtuel avec attribut not set faisaient planter la passerelle d'import
		// maintenant on supprime les petits segments temporaire créés pour relier le node virtuel
		// attention : on ne nettoie que les vecteurs de Path, mais on laisse les vecteurs de segments inchangés
		ArrayList<PathREF> vPathREF_new = new ArrayList<>(vPathREF.size());
		ArrayList<PathDER> vPathDER_new = new ArrayList<>(vPathDER.size());
		for (PathREF path : vPathREF)
			if (!path.getAttributes().hasValueNOTSET())
				vPathREF_new.add(path);
		for (PathDER path : vPathDER)
			if (!path.getAttributes().hasValueNOTSET())
				vPathDER_new.add(path);
		vPathDER = vPathDER_new;
		vPathREF = vPathREF_new;
		spiPathDER.clear();
		spiPathDER.addObjects(vPathDER);
		spiPathREF.clear();
		spiPathREF.addObjects(vPathREF);
		
		// On incrémente la version des données
		versionDataDER++;
		
		// Enfin, pour s'assurer que les nodes fraichement créés ont bien la bonne taille, on simule un faux changement d'échelle
		setEchelleCarteCourante (echelleCarteCourante_inv);
	}
}
